
#include "enet.hpp"
#include "pendina_trx_core.h"

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

#include <pthread.h>
#ifdef WIN32
#pragma comment(lib, "pthreadVC2.lib")
#endif

//
//

class pendina_standalone_endpoint
{
	friend class pendina_standalone;
private:
	pendina_standalone_endpoint() : 
	local_host(0),
	local_peer(0),
	connected_peers_count(0),
	reset_flag(false)
	{

	}

private:
	unsigned int connected_peers_count;

	pthread_mutex_t incoming_lock;
	pthread_mutex_t outgoing_lock;
	pthread_t transceiver_thread;
	bool reset_flag;

	ENetHost *local_host;
	ENetPeer *local_peer;
};

pendina_standalone::pendina_standalone() : 
current_state(pendina_idle_state),
is_server(false),
in_session(false),
_internal_id(0),
endpoint(0)
{
	enet_initialize();

	_incomingStream = new NetworkStream();
	_outgoingStream = new NetworkStream();

	endpoint = new pendina_standalone_endpoint();
	assert(endpoint);
}

pendina_standalone::~pendina_standalone()
{
	assert(endpoint);

	pendina_destroy();
	enet_deinitialize();
	delete endpoint;
	endpoint = 0;
}

void pendina_standalone::reset()
{
	assert(endpoint);
	current_state = pendina_reset_state;
}

void pendina_standalone::start(bool servermode)
{
	assert(endpoint);

	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	is_server = servermode;
	current_state = pendina_setup_state;

	pthread_mutex_init(&endpoint->incoming_lock, NULL);
	pthread_mutex_init(&endpoint->outgoing_lock, NULL);

	int rc = pthread_create(&endpoint->transceiver_thread, &attr, internal_run, this); 
	if (rc != 0) {
		printf("ERROR: return code from pthread_create() is %d\n", rc);
	}
	pthread_attr_destroy(&attr);
	printf("+ transceiver thread started\n");
}

void pendina_standalone::stop()
{
	assert(endpoint);
	
	void *status;
	current_state = pendina_exit_state;
	int rc = pthread_join(endpoint->transceiver_thread, &status);
	if (rc) {
		printf("ERROR: return code from pthread_join() is %d\n", rc);
	}

	pthread_mutex_destroy(&endpoint->incoming_lock);
	pthread_mutex_destroy(&endpoint->outgoing_lock);

	_incomingStream->Reset();
	_outgoingStream->Reset();

	printf("+ transceiver thread terminated\n");
}

void pendina_standalone::pendina_idle()
{
	assert(endpoint);
	printf ("%s\n", __FUNCTION__);

	::Sleep(100);
}

void pendina_standalone::raise_incoming_event(unsigned char *data, unsigned int len)
{
	pthread_mutex_lock(&endpoint->incoming_lock);
	_incomingStream->WriteRaw(data, len);
	pthread_mutex_unlock(&endpoint->incoming_lock);
}

void pendina_standalone::raise_incoming_event(SmartPtr<NetworkStream> _str)
{
	pthread_mutex_lock(&endpoint->incoming_lock);
	_incomingStream->WriteRaw(_str->GetStreamData(), _str->GetStreamDataLength());
	pthread_mutex_unlock(&endpoint->incoming_lock);
}

void pendina_standalone::raise_outgoing_event(unsigned char *data, unsigned int len)
{
	pthread_mutex_lock(&endpoint->outgoing_lock);
	_outgoingStream->WriteRaw(data, len);
	pthread_mutex_unlock(&endpoint->outgoing_lock);
}

void pendina_standalone::raise_outgoing_event(SmartPtr<NetworkStream> _str)
{
	pthread_mutex_lock(&endpoint->outgoing_lock);
	_outgoingStream->WriteRaw(_str->GetStreamData(), _str->GetStreamDataLength());
	pthread_mutex_unlock(&endpoint->outgoing_lock);
}

SmartPtr<NetworkStream> pendina_standalone::get_incoming_stream()
{
	SmartPtr<NetworkStream> str = new NetworkStream();
	pthread_mutex_lock(&endpoint->incoming_lock);
	if(_incomingStream->GetStreamDataLength())
	{
		str->WriteRaw(_incomingStream->GetStreamData(), _incomingStream->GetStreamDataLength());
		_incomingStream->Reset();
	}
	pthread_mutex_unlock(&endpoint->incoming_lock);
	return str;
}

SmartPtr<NetworkStream> pendina_standalone::get_outgoing_stream()
{
	SmartPtr<NetworkStream> str = new NetworkStream();
	pthread_mutex_lock(&endpoint->outgoing_lock);
	if(_outgoingStream->GetStreamDataLength())
	{
		str->WriteRaw(_outgoingStream->GetStreamData(), _outgoingStream->GetStreamDataLength());
		_outgoingStream->Reset();
	}
	pthread_mutex_unlock(&endpoint->outgoing_lock);
	return str;
}

void pendina_standalone::send_outgoing_stream(SmartPtr<NetworkStream> &str)
{
	printf ("%s\n", __FUNCTION__);
	raise_outgoing_event(str);
}

void pendina_standalone::pendina_setup()
{
	assert(endpoint);
	//printf ("%s\n", __FUNCTION__);

	srand(enet_time_get());
	_internal_id = rand();

	ENetAddress address;

	if(is_server)
	{
		address.host = ENET_HOST_ANY;
		address.port = 30000;

		endpoint->local_host = enet_host_create(&address, 32, ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT, 0, 0);
		if(endpoint->local_host)
		{
			printf ("+ Local server %d\n", _internal_id);
			current_state = pendina_service_state;
			in_session = true;
			is_server = true;
		}
	}

	if(!is_server || !endpoint->local_host)
	{
		assert(endpoint->local_host == NULL);

		enet_address_set_host(&address, "localhost");
		address.port = 30000;

		endpoint->local_host = enet_host_create(NULL, 1, ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT, 57600 / 8, 14400 / 8);
		endpoint->local_peer = enet_host_connect(endpoint->local_host, &address, ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT, _internal_id);
		if (endpoint->local_peer == NULL)
			current_state = pendina_destroy_state;
		else
		{
			current_state = pendina_service_state;
			in_session = false;
			is_server = false;
		}
	}

	assert(endpoint->local_host);
}

void pendina_standalone::pendina_service()
{
	if(current_state != pendina_service_state)
		return;

	assert(endpoint);
	assert(endpoint->local_host);

	if(in_session)
	{
		SmartPtr<NetworkStream> str = get_outgoing_stream();
		unsigned int ret = str->GetStreamDataLength();
		if(ret>0)
		{
			ENetPacket * packet = enet_packet_create(str->GetStreamData(), str->GetStreamDataLength(), ENET_PACKET_FLAG_RELIABLE);
			enet_host_broadcast (endpoint->local_host, 0, packet);
		}
	}

	ENetEvent event;
	if(enet_host_service(endpoint->local_host, &event, 100) <= 0)
		return;

	switch (event.type)
	{
	case ENET_EVENT_TYPE_RECEIVE:
		{
			if (event.channelID == 0)
			{
				if(!is_server)			
				{
					SmartPtr<NetworkStream> str = new NetworkStream();
					str->WriteRaw(event.packet->data, event.packet->dataLength);
					raise_incoming_event(str);
				}
			}

			//printf("+ received\n");
			enet_packet_destroy (event.packet);
		}
		break;
	case ENET_EVENT_TYPE_CONNECT:
		{
			SmartPtr<NetworkStream> str = new NetworkStream();
			str->WriteLong(ENET_EVENT_TYPE_CONNECT);
			if(is_server)
			{
				printf("+ client connected(%u)\n",event.peer->pendinaID);
				str->WriteLong(event.peer->pendinaID);
				str->WriteLong(event.peer->address.host);
				str->WriteLong(event.peer->address.port);
				raise_incoming_event(str);
				raise_outgoing_event(str);
			}
			else
			{
				//printf("+ connected to server\n");
				printf ("+ Local client %d\n", _internal_id);
				in_session = true;
			}
		}
		break;
	case ENET_EVENT_TYPE_DISCONNECT:
		{
			SmartPtr<NetworkStream> str = new NetworkStream();
			str->WriteLong(ENET_EVENT_TYPE_DISCONNECT);
			if(is_server)
			{
				printf("+ client disconnected(%u)\n",event.peer->pendinaID);
				str->WriteLong(event.peer->pendinaID);
				str->WriteLong(event.peer->address.host);
				str->WriteLong(event.peer->address.port);
				raise_incoming_event(str);
				raise_outgoing_event(str);
			}
			else
			{
				current_state = pendina_reset_state;
				if(!in_session)
				{
					printf("+ unable to connect to server\n");
					is_server = true;
				}
				else
				{
					printf("+ disconnected from server\n");
				}
			}
		}
		break;
	default:
		break;
	}
}

void pendina_standalone::pendina_destroy()
{
	assert(endpoint);
	//printf ("%s\n", __FUNCTION__);

	if(endpoint->local_host)
	{
		if(endpoint->local_peer)
		{
			enet_peer_disconnect(endpoint->local_peer, 0);
			enet_host_flush(endpoint->local_host);
			enet_peer_reset(endpoint->local_peer);
			endpoint->local_peer = NULL;
		}

		enet_host_flush(endpoint->local_host);
		enet_host_destroy(endpoint->local_host);
		endpoint->local_host = NULL;
	}

	if(current_state == pendina_reset_state)
		current_state = pendina_setup_state;
	else
		current_state = pendina_idle_state;

	assert(endpoint->local_peer == NULL);
	assert(endpoint->local_host == NULL);
}

pendina_state pendina_standalone::pendina_run()
{
	assert(endpoint);

	::Sleep(1); // not to eat up all cpu time when in this state
	return current_state;
}

void* pendina_standalone::internal_run(void *arg)
{
	pendina_standalone *pref = (pendina_standalone *)arg;
	bool keep_running = true;

	while(keep_running)
	{
		switch( pref->pendina_run() )
		{
		case pendina_idle_state:
			pref->pendina_idle();
			break;
		case pendina_setup_state:
			pref->pendina_setup();
			break;
		case pendina_service_state:
			pref->pendina_service();
			break;
		case pendina_destroy_state:
			pref->pendina_destroy();
			break;
		case pendina_reset_state:
			pref->pendina_destroy();
			break;
		case pendina_exit_state:
			pref->pendina_destroy();
			keep_running = false;
			break;
		default:
			break;
		}
	}

	return (void *)NULL;
}
